package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"strings"
	"time"
	"yhzn/exchange-cabinet-device/cabinet"
	web_socket "yhzn/exchange-cabinet-device/web-socket"

	"zym.com/tools/zfile"

	"zym.com/tools/zconvert/json"
)

// Conf 配置信息
type Conf struct {
	DevId                string `json:"dev_id"`                 // 设备ID
	DevStatusInterval    uint16 `json:"dev_status_interval"`    // 读取设备状态间隔时长
	HeartbeatInterval    uint16 `json:"heartbeat_interval"`     // 心跳间隔时长
	UploadStatusInterval uint16 `json:"upload_status_interval"` // 上传状态间隔时长
	ApiDomain            string `json:"api_domain"`             // api域名
	ApiPort              int    `json:"api_port"`               // api端口
	MqttDomain           string `json:"mqtt_domain"`            // mqtt域名
	MqttPort             int    `json:"mqtt_port"`              // mqtt端口
	MqttUser             string `json:"mqtt_user"`              // mqtt用户
	MqttPw               string `json:"mqtt_pw"`                // mqtt密码
	CabinetSum           uint8  `json:"cabinet_sum"`            // 仓门数量
	Repetition           uint8  `json:"repetition"`             // 重发次数
	SerialPort           string `json:"serial_port"`            // 串口名称
	OperatorPassword     string `json:"operator_password"`      // 运维密码
	QrCode               string `json:"qr_code"`                // 设备二维码
	HelpPhone            string `json:"help_phone"`             // 帮助电话
	Sn                   string `json:"sn"`                     // 设备自编号
	Prefix               string `json:"prefix"`                 // mqtt topic前辍
	VendorId             string `json:"vendor_id"`              // mqtt topic 厂商ID
	Dpi                  int    `json:"dpi"`                    // 显示屏分辨率 1：800*6600 2：1024*768 3：1920*1080
	Yh                   int    `json:"yh"`                     // 是否为云科设备
	Version              string `json:"version"`                //
	Zone                 string `json:"zone"`                   // 时区
}

var cfg Conf

// const version = "2.4.2.2"
// const version = "2.4.2.3" // 1、远程修改mqtt连接地址、端口，验证新的地址是否可用。不可用时不接受命令。2、增加可修改MQTT用户和密码
// const version = "2.4.2.4" // 增加写报错日志功，mqtt可获取日志文件列表，获取日志内容，日志保存5天
// const version = "2.4.2.5" // 增加一条MQTT连接，连接至云禾服务器
const version = "2.4.2.8" // 增加读取4g模块信息（信号强度，CCID，CIMI）
// 定时器
var (
	devStatusTimer    *time.Ticker // 读取设备状态时钟
	heartbeatTimer    *time.Ticker // 心跳时种
	uploadStatusTimer *time.Ticker // 上报状态时钟
)

const (
	dir         = "f"                     // 本地持久目录
	oldConfFile = dir + "/conf.txt"       // 老的配置文件
	statusFile  = dir + "/statusList.txt" // 分板状态
	//confFile    = dir + "/config.json"  // 配置文件
)

var (
	confFile = dir + "/config.json" // 配置文件
)

const (
	caFile   = "f/cert/ca.pem"     //
	certFile = "f/cert/cert.crt"   //
	keyFile  = "f/cert/client.crt" //
)

const (
	defaultVendorId = "1492349f-4e47-49df-8999-8ba4bf4e3ab3"
	defaultZone     = "Asia/Shanghai"
	windows         = "windows"
	linux           = "linux"
)

const (
	AtCCID = "+CCID:"
	AtCSQ  = "+CSQ:"
)

func main() {
	defer func() {
		if p := recover(); p != nil {
			debug.PrintStack()
			writeLog(string(debug.Stack()))
		}
	}()
	netInfo.New()

	//get4gCsq()
	// 创建文件存储目录
	createDir()

	// 获取配置信息
	readConfig()
	cfg.Version = version
	writeConfig()

	//copy("cabinet.sh", "/etc/profile.d/cabinet.sh") // 老板
	_, _ = copyDF("cabinet.sh", "/home/ys_rk3568/cabinet.sh") // 新板
	runCmd([]string{"chmod", "777", "cabinet.sh"}...)         //
	runCmd([]string{"chmod", "777", "check4g.sh"}...)

	setZone()

	go WebServerLister()

	// 读取本地存储的设备状态
	readLocalStatus()

	// 打开485通迅
	e := cabinet.Run(cfg.SerialPort)

	if e != nil {
		fmt.Println("串口打开失败:", e)
		//os.Exit(0)
	} else {
		fmt.Println("串口打开成功")
	}

	// mqtt
	go func() {
		// 连接MQTT服务器
		if e := connectMqtt(true); e != nil {
			fmt.Println("mqtt 连接失败", e)
			return
		}
		fmt.Println("mqtt connect success:", cfg.MqttDomain)
	}()
	go func() {
		e = connectYhMqtt()
	}()

	// websocket
	web_socket.Init()
	fmt.Println("websocket初始化")

	// 启动轮询读取换电柜状态线程
	go readStatusThread()
	// 启动上传心跳线程
	go uploadHeartbeatThread()
	// 启动上传状态线程
	go uploadStatusThread()
	// 网络检测
	go check4gNet()
	// 程序启动10秒后通知刷新web页
	go func() {
		time.Sleep(10 * time.Second) //
		web_socket.Write("refresh")  // 通知web刷新页面
		//web_socket.Hint("update software", 0)
		//time.Sleep(10 * time.Second) //
		//web_socket.Hint("update software...............", 0)
	}()
	//

	go read4gInfo()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Kill, os.Interrupt)
	<-c

	mqttClient.Close()

	cabinet.Stop()

}

// 创建目录
func createDir() {
	if !zfile.DirExists(dir) {
		zfile.CreateDirectory(dir)
	}
}

// 读取配置
func readConfig() Conf {
	if zfile.DirExists(oldConfFile) {
		confFile = oldConfFile
	}
	b, e := zfile.ReadFile(confFile)
	if e != nil {
		zfile.WriteFile(confFile, nil)
	}
	e = json.ToObj(string(b), &cfg)
	if e != nil {
		return cfg
	}
	if cfg.DevId == "" {
		id := ""
		fmt.Println("请输出设备编号")
		for true {
			if _, e = fmt.Scan(&id); e == nil {
				cfg.DevId = id
				cfg.QrCode = id
				cfg.OperatorPassword = "123456"
				if j, e := json.ToJson(cfg); e == nil {
					zfile.WriteFile(confFile, []byte(j))
				}
				break
			}
		}
	}
	if cfg.Zone == "" {
		cfg.Zone = defaultZone
	}

	if cfg.VendorId == "" {
		cfg.VendorId = defaultVendorId
	}
	return cfg
}

// 写配置文件
func writeConfig() (t bool) {
	if j, e := json.ToJson(cfg); e == nil {
		zfile.WriteFile(confFile, []byte(j))
	} else {
		t = false
	}
	return
}

// 读取本地存储的设备状态
func readLocalStatus() {
	b, e := zfile.ReadFile(statusFile)
	if e != nil {
		return
	}
	_ = json.ToObj(string(b), &cab)
}

func copyDF(src, dst string) (int64, error) {
	sourceFileStat, err := os.Stat(src)
	if err != nil {
		return 0, err
	}

	if !sourceFileStat.Mode().IsRegular() {
		return 0, fmt.Errorf("%s is not a regular file", src)
	}

	source, err := os.Open(src)
	if err != nil {
		return 0, err
	}
	defer func() {
		err = source.Close()
	}()

	destination, err := os.Create(dst)
	if err != nil {
		return 0, err
	}
	defer func() {
		err = destination.Close()
	}()

	nBytes, err := io.Copy(destination, source)
	return nBytes, err
}

//var isNet = false
//var isWifi = false
//var isMqtt = false

type NetworkInfo struct {
	NetConnected bool   `json:"-"`        // 网络是否连接
	NetType      byte   `json:"net_type"` // 网络连接类型 0：未知，1：WIFI，2：4G 3：以太
	MqttConnect  bool   `json:"-"`        // MQTT服务是否连接
	CSQ          string `json:"CSQ"`      // 信号强度
	CCID         string `json:"CCID"`     //
	CIMI         string `json:"CIMI"`     //
	chNetType    chan byte
	chCSQ        chan byte
}

func (c *NetworkInfo) New() {
	c.chNetType = make(chan byte, 1)
	c.chCSQ = make(chan byte, 1)
}

func (c *NetworkInfo) SetNetType(v byte) {
	c.chNetType <- 0x01
	defer func() {
		<-c.chNetType
	}()
	c.NetType = v
}
func (c *NetworkInfo) SetCSQ(v string) {
	c.chCSQ <- 1
	defer func() {
		<-c.chCSQ
	}()
	c.CSQ = v
}

var netInfo NetworkInfo
var isRegister bool

func check4gNet() {
	timer := time.NewTicker(10 * time.Second)
	systemType := runtime.GOOS
	var cmd *exec.Cmd

	for {
		select {
		case <-timer.C:
			//get4gCsq()
			if !netInfo.MqttConnect {
				if systemType == windows {
					cmd = exec.Command("ping", "baidu.com", "-n", "1", "-l", "1")
				} else if systemType == linux {
					cmd = exec.Command("ping", "baidu.com", "-c", "1", "-s", "1")
				} else {
					return
				}
				if e := cmd.Run(); e != nil {
					netInfo.NetConnected = false
				} else {
					netInfo.NetConnected = true
				}
			} else {
				netInfo.NetConnected = true
			}
			if netInfo.NetConnected || netInfo.MqttConnect {
				var netType byte = 0
				var cqs = ""
				if wifi, e := GetInUseWifiList(); e == nil {
					for _, v := range wifi {
						//fmt.Printf("%#v\n", v)
						if strings.Contains(v.Inuse, "*") && v.Signal != "0" {
							netType = 1
							cqs = v.Signal
							break
						}
					}
				}
				if netType == 1 {
					netInfo.SetCSQ(cqs)
					netInfo.SetNetType(netType)
					if !isRegister {
						uploadRegister(mqttClient.Cli)
						isRegister = true
					}
				} else {
					get4gCsq()
					time.Sleep(5 * time.Second)
					if !isRegister {
						uploadRegister(mqttClient.Cli)
						isRegister = true
					}
				}
			}
		}
	}
}

func runCmd(arg ...string) {
	cmd := exec.Command("sudo", arg...)
	_ = cmd.Run()
}

func writeLog(content string) {
	// 获取当前时间
	currentTime := time.Now()

	// 将当前时间向前推三天
	threeDaysAgo := currentTime.AddDate(0, 0, -5)

	if !zfile.DirExists("logs") {
		zfile.CreateDirectory("logs")
	}
	// 遍历文件夹路径下的所有文件
	_ = filepath.Walk("logs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 如果文件在三天前创建或修改，删除文件
		if info.ModTime().Before(threeDaysAgo) {
			err = os.Remove(path)
			if err != nil {
				fmt.Println(err)
			}
		}
		return nil
	})
	fileName := currentTime.Format("2006-01-02")
	zfile.WriteTextFile("./logs/"+fileName, content+"\n")
}

func execCmd(args []string) (ret string) {
	// 要执行的命令
	fmt.Println("执行：", args)
	var cmd *exec.Cmd
	if args[0] == "cd" || args[0] == "cat" {
		cmd = exec.Command(args[0], args[1:]...)
	} else {
		cmd = exec.Command("sudo", args...)
	}

	// 执行命令并获取输出
	output, e := cmd.Output()
	if e != nil {
		fmt.Println("exec command error:", e)
		ret = e.Error()
		return
	}
	ret = string(output)
	return
}

// 设置时区
func setZone() {
	execCmd([]string{"timedatectl", "set-timezone", cfg.Zone})
}

func get4gCsq() {
	fmt.Println("执行check4g")
	var cmd *exec.Cmd
	cmd = exec.Command("/bin/sh", "./check4g.sh")
	_, e := cmd.Output()
	if e != nil {
		fmt.Println("执行check4g:", e)
	}
}

func read4gInfo() {
	var cmd *exec.Cmd
	cmd = exec.Command("sudo", "cat", "/dev/ttyUSB2")
	stdout, e := cmd.StdoutPipe()
	if e != nil {
		return
	}
	defer func() {
		_ = stdout.Close()
	}()
	_ = cmd.Start()
	var ret = make([]byte, 100)
	for {
		n, e := stdout.Read(ret)
		if e != nil {
			time.Sleep(500 * time.Millisecond)
			continue
		}

		// fmt.Println("rec:", n, "->", string(ret[:n]))
		str := string(ret[:n])

		if strings.Contains(str, AtCSQ) {
			pos1 := strings.Index(str, AtCSQ)
			pos2 := strings.Index(str, ",")
			if pos1 >= 0 {
				netInfo.SetCSQ(str[pos1+len(AtCSQ)+1 : pos2])
				netInfo.CSQ = clearChat(&netInfo.CSQ)
			}
			netInfo.SetNetType(2)
		} else if strings.Contains(str, AtCCID) {
			pos := strings.Index(str, AtCCID)
			if pos >= 0 {
				netInfo.CCID = str[pos+len(AtCCID)+1:]
				netInfo.CCID = clearChat(&netInfo.CCID)
			}
		} else if strings.Contains(str, "CIMI") && len(str) > 8 {
			str = strings.ReplaceAll(str, "AT+CIMI", "")
			tmp := clearChat(&str)
			netInfo.CIMI = tmp

		} else {
			tmp := clearChat(&str)
			if len(str) == 15 {
				netInfo.CIMI = tmp
			}

		}
		time.Sleep(100 * time.Millisecond)
	}
}

func clearChat(str *string) string {
	*str = strings.ReplaceAll(*str, "\r", "")
	*str = strings.ReplaceAll(*str, "\n", "")
	*str = strings.ReplaceAll(*str, "OK", "")
	*str = strings.TrimSpace(*str)
	return *str
}
